Saltar al contenido principal

Unity WebGL con compresión Brotli: HTTP headers por plataforma

Esta guía cubre el despliegue de builds de Unity WebGL que usan compresión Brotli (o Gzip): qué pedirle al programador de Unity antes del build, por qué son necesarios los headers HTTP, y cómo configurarlos según la plataforma de hosting.

¿Para qué sirve?

Unity WebGL puede compilar su build con tres modos de compresión. Cada modo genera archivos con extensiones distintas y, en dos de los tres casos, requiere que el servidor envíe un header Content-Encoding correcto para que el navegador pueda descomprimirlos.

ModoExtensión de archivosRequiere headers del servidor
Brotli.wasm.br, .framework.js.br, .data.brContent-Encoding: br
Gzip.wasm.gz, .framework.js.gz, .data.gzContent-Encoding: gzip
Sin compresión.wasm, .js, .dataNo (solo Content-Type para .wasm)

Cuando el servidor no envía el Content-Encoding correcto, el navegador recibe bytes comprimidos que trata como datos crudos. Los errores típicos son:

Unable to load file Build/MyGame.framework.js.br!
both async and sync fetching of the wasm failed

Estos errores no son del código de la app — son de headers HTTP faltantes o incorrectos.

Qué pedirle al programador de Unity antes del build

Antes de recibir el build, confirmar estos dos puntos con quien lo genera.

Tipo de compresión

Pedir que informe explícitamente si el build usa Brotli, Gzip o ninguna. Esto determina toda la configuración del servidor.

Ubicación en Unity: Project Settings → Player → WebGL → Publishing Settings → Compression Format

Decompression Fallback (opción crítica)

En el mismo menú existe la opción Decompression Fallback. Si está activada, Unity incluye un descompresor JavaScript dentro del build: el navegador puede descomprimir los archivos sin necesidad de headers del servidor.

Pedir activada cuando:

  • El hosting es Namecheap/cPanel con LiteSpeed (ver más abajo).
  • No se puede controlar los headers HTTP de la plataforma.
  • Entorno de desarrollo rápido sin configuración de servidor.

Tradeoff: Agrega ~0.5–2 segundos al tiempo de carga inicial. No afecta el rendimiento del juego en runtime.

No activar cuando:

  • Se controla el servidor y se pueden configurar los headers correctos.
  • El tamaño del build importa (Decompression Fallback lo agranda).

Regla práctica: Si no se conoce la plataforma de destino al momento del build, pedir con Decompression Fallback activado. Es el modo más robusto — elimina toda dependencia de configuración del servidor.

Headers HTTP requeridos (referencia universal)

Archivos .framework.js.br

Content-Encoding: br
Content-Type: application/javascript

Archivos .wasm.br

Content-Encoding: br
Content-Type: application/wasm

Archivos .data.br

Content-Encoding: br
Content-Type: application/octet-stream

Archivos .br (regla genérica para todos)

Content-Encoding: br
Cache-Control: max-age=31536000

Archivos .gz (regla genérica para todos)

Content-Encoding: gzip
Cache-Control: max-age=31536000

Archivos .wasm (sin compresión)

Content-Type: application/wasm

Por qué Content-Type: application/wasm es obligatorio: Chrome y Firefox rechazan instanciar WebAssembly si el MIME type es application/octet-stream. El build falla silenciosamente o lanza wasm streaming compile failed.

Configuración por plataforma

Netlify

Método: Archivo netlify.toml en la raíz del repositorio.

[[headers]]
for = "/*.gz"
[headers.values]
Content-Encoding = "gzip"
Cache-Control = "max-age=31536000"

[[headers]]
for = "/*.br"
[headers.values]
Content-Encoding = "br"
Cache-Control = "max-age=31536000"

[[headers]]
for = "*.wasm"
[headers.values]
Content-Type = "application/wasm"

[[headers]]
for = "*.wasm.gz"
[headers.values]
Content-Type = "application/wasm"
Content-Encoding = "gzip"

[[headers]]
for = "*.wasm.br"
[headers.values]
Content-Type = "application/wasm"
Content-Encoding = "br"

Notas:

  • Netlify aplica estas reglas sin conflictos ni re-compresión de archivos .br/.gz existentes.
  • Es la plataforma donde esta configuración funciona de forma más directa y predecible.
  • No se necesita ninguna configuración adicional en el dashboard.

Render (Static Site)

Método: Dashboard → Static Site → pestaña Headers.

Diferencia crítica vs Netlify: El wildcard /*.br en Render solo matchea archivos en la raíz del sitio. Los builds de Unity viven en subcarpetas (ej. /unity/MiJuego/Build/MiJuego.wasm.br), por lo que el patrón correcto es /**/*.br con doble asterisco.

Configuración real (verificada):

Request PathHeader NameHeader Value
/*.brContent-Encodingbr
/*.brCache-Controlmax-age=31536000
/*.gzContent-Encodinggzip
/*.gzCache-Controlmax-age=31536000
/*.wasmContent-Typeapplication/wasm
/*.wasm.brContent-Typeapplication/wasm
/*.wasm.brContent-Encodingbr
/*.wasm.gzContent-Typeapplication/wasm
/*.wasm.gzContent-Encodinggzip
/*.framework.js.brContent-Typeapplication/javascript
/*.framework.js.brContent-Encodingbr
/*.data.brContent-Typeapplication/octet-stream
/*.data.brContent-Encodingbr

Problema conocido — doble compresión: Render puede tener compresión Brotli automática. Si ya sirve un archivo .br pre-comprimido, puede intentar comprimirlo de nuevo. Síntoma: el build falla incluso con los headers correctos (ver diagnóstico más abajo).

Problema conocido — CDN por delante (F5/ves.io, Cloudflare): Si hay un CDN frente a Render, puede cachear respuestas antiguas (sin los headers nuevos) aunque ya se haya redesplegado. Solución: purgar caché del CDN después de cada deploy, o testear directo sobre la URL .onrender.com para aislar si el problema es el CDN o Render.

BunnyCDN

BunnyCDN tiene dos componentes que se usan juntos para servir Unity WebGL.

Paso 1 — Crear un Storage Zone. El Storage Zone es donde se suben físicamente los archivos del build.

  • BunnyCDN Dashboard → StorageAdd Storage Zone
  • Nombre: ej. bhuegol
  • Tier: Standard
  • Replication: activado (recomendado)
  • Subir los archivos del build de Unity directamente al Storage Zone

El Storage Zone actúa como origin. No tiene URL pública directa para servir al browser — para eso se necesita el Pull Zone.

Paso 2 — Crear un Pull Zone conectado al Storage Zone. El Pull Zone es la capa CDN que sirve los archivos al browser con caché global.

  • BunnyCDN Dashboard → CDNAdd Pull Zone
  • Nombre: ej. bhuegol
  • Origin: seleccionar el Storage Zone bhuegol (no una URL externa)
  • Esto genera una URL pública como https://bhuegol.b-cdn.net

Paso 3 — Configurar Edge Rules en el Pull Zone. Los headers no se configuran en el Storage Zone sino en el Pull Zone → Edge Rules.

Edge Rule 1 — .data.br

Condition: Request URL matches wildcard → *.data.br*
Actions:
- Set Response Header → Content-Encoding: br
- Set Response Header → Content-Type: application/octet-stream

Edge Rule 2 — .wasm.br

Condition: Request URL matches wildcard → *.wasm.br*
Actions:
- Set Response Header → Content-Encoding: br
- Set Response Header → Content-Type: application/wasm

Edge Rule 3 — .framework.js.br

Condition: Request URL matches wildcard → *.framework.js.br*
Actions:
- Set Response Header → Content-Encoding: br
- Set Response Header → Content-Type: application/javascript

El orden importa: colocar las reglas más específicas primero (.wasm.br antes que .br genérico). Si se agregan reglas para .gz, seguir el mismo patrón.

Importante — desactivar re-compresión: En el Pull Zone → Optimizer, desactivar cualquier opción de compresión automática sobre los paths del build. BunnyCDN puede intentar re-comprimir archivos .br ya comprimidos y corromper el contenido.

Ventaja clave de BunnyCDN: El CDN está downstream del storage y puede inyectar headers independientemente del origin. Esto permite servir Unity correctamente desde Azure Blob Storage, Namecheap, o cualquier origin que no soporte configuración de headers. Cache HIT rate esperado: ~99%+ una vez calentado.

Azure Blob Storage

Azure Blob Storage en modo Static Website no permite configurar Content-Encoding por archivo desde el portal. Servir un build Brotli de Unity directamente desde Azure sin CDN no es viable salvo que el build tenga Decompression Fallback.

Solución estándar: Usar Azure Blob como storage y colocar BunnyCDN como Pull Zone apuntando al Blob como origin, luego configurar las Edge Rules en BunnyCDN.

Alternativa sin CDN: Solicitar el build con Decompression Fallback activado.

Namecheap / cPanel — caso problemático (LiteSpeed)

Namecheap shared hosting corre LiteSpeed en lugar de Apache puro. Esto genera un problema específico e irresolvible con .htaccess.

El problema

LiteSpeed lee el .htaccess de forma compatible con Apache (por eso AddType/Content-Type funciona), pero maneja la compresión internamente y descarta el header Content-Encoding que se intente definir vía .htaccess.

Síntomas:

  • Content-Type se aplica correctamente ✅
  • Content-Encoding: br no aparece en los response headers ❌
  • Los headers muestran server: LiteSpeed y x-turbo-charged-by: LiteSpeed

Cómo confirmar:

curl -I https://tu-subdominio.com/Build/MiJuego.wasm.br | grep -i "server\|x-turbo\|content-encoding"

Qué no funciona en LiteSpeed (no perder tiempo en esto)

Ninguna de estas directivas logra poner Content-Encoding: br:

AddEncoding br .br                       # ignorado
Header set Content-Encoding "br" # ignorado
Header always set Content-Encoding "br" # ignorado

LiteSpeed administra compresión internamente y no lo delega al .htaccess en shared hosting (requeriría acceso WHM/root que no existe en planes compartidos).

Soluciones

Opción A — Decompression Fallback (recomendada): Pedir al programador Unity que regenere el build con Decompression Fallback activado. No requiere ningún header del servidor.

Opción B — BunnyCDN por delante del subdominio: Crear un Pull Zone en BunnyCDN con el subdominio de Namecheap como origin URL, y configurar las Edge Rules ahí. El CDN actúa como intermediario e inyecta los headers que LiteSpeed no puede proveer.

Diagnóstico rápido (checklist)

Cuando un build de Unity WebGL falla al cargar, seguir este orden:

Paso 1 — Identificar el error en consola

  • Unable to load file Build/...Content-Encoding faltante o incorrecto.
  • both async and sync fetching of the wasm failedContent-Type: application/wasm faltante o Content-Encoding incorrecto.
  • Failed to fetch con status 404 → el archivo no existe en el path.

Paso 2 — Revisar headers de respuesta

En DevTools → Network → seleccionar el archivo .wasm.br → Response Headers. Deben aparecer:

content-encoding: br
content-type: application/wasm

O con curl:

curl -I -H "Accept-Encoding: br" https://tu-dominio.com/Build/MiJuego.wasm.br

Paso 3 — Identificar el servidor

  • server: LiteSpeed + x-turbo-charged-by: LiteSpeed → Namecheap, .htaccess no sirve para Content-Encoding.
  • server: cloudflare → configurar Page Rules o Transform Rules en Cloudflare.
  • Sin server header → probablemente Render o similar, configurar en dashboard.

Paso 4 — Aislar capas. Si hay CDN por delante, testear directamente la URL del origin (ej. .onrender.com, bhuegol.b-cdn.net) para saber si el problema viene del CDN o del origin.

Paso 5 — Verificar caché. Probar siempre en ventana de incógnito. Un hit de caché puede mostrar headers viejos aunque ya se haya redesplegado. En BunnyCDN usar el botón Purge Cache del Pull Zone después de cambiar Edge Rules.

Resumen de decisión por plataforma

¿En qué plataforma se hace el deploy?

├── Netlify
│ └── netlify.toml con [[headers]] en la raíz ✅
│ Más simple y predecible de todas las opciones.

├── Render (Static Site)
│ └── Dashboard → Headers, usar /*.br (no /*.br) ✅
│ Si hay CDN por delante: purgar caché del CDN tras deploy.

├── BunnyCDN como CDN principal
│ ├── Paso 1: Crear Storage Zone y subir los archivos del build
│ ├── Paso 2: Crear Pull Zone apuntando al Storage Zone
│ └── Paso 3: Edge Rules por extensión (.data.br, .wasm.br, .framework.js.br) ✅
│ Desactivar Optimizer/auto-compresión en el Pull Zone.

├── Azure Blob Storage
│ └── No soporta Content-Encoding nativo → necesita BunnyCDN por delante ✅
│ O usar Decompression Fallback en el build.

└── Namecheap / cPanel (LiteSpeed)
├── NO usar .htaccess para Content-Encoding — LiteSpeed lo ignora ❌
├── Opción A: Decompression Fallback en el build ✅ (recomendada)
└── Opción B: BunnyCDN Pull Zone apuntando al subdominio como origin ✅